//
//  SocketRocketUtility.m

//

#import "SocketRocketUtility.h"
#import <sys/utsname.h>

#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}


@interface SocketRocketUtility()<SRWebSocketDelegate>
{
    int _index;
    NSTimer * heartBeat;
    NSTimeInterval reConnectTime;
}

@property (nonatomic,strong) SRWebSocket *socket;

@property (nonatomic,copy) NSString *urlString;
///是否已经建立连接
@property (nonatomic,assign) BOOL  isConnectOK;

@end

@implementation SocketRocketUtility

+(SocketRocketUtility *)instance{
    static SocketRocketUtility *Instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        Instance = [[SocketRocketUtility alloc] init];
    });
    return Instance;
}

#pragma mark - **************** public methods
-(void)SRWebSocketOpenWithURLString:(NSString *)urlString {
    [self SRWebSocketClose];
    
    //如果是同一个url return
    if (self.socket) {
        return;
    }
    if (!urlString) {
        return;
    }
    
    self.urlString = urlString;
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5];
    NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"NHTPushDeviceToken"];
    if (!token || token.length < 1){
        NSLog(@"获取deviceToken失败，无法连接socket");
        self.socketStateChange(self.socketReadyState);
        return;
    }
    
    token = [@"apns-" stringByAppendingString:token];
    [request setValue:token forHTTPHeaderField:@"token"];
    
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *CFBundleDisplayName = [infoDictionary objectForKey:@"CFBundleIdentifier"];
    NSMutableString *extra_info = @"app:".mutableCopy;//bundle id
    [extra_info appendString:CFBundleDisplayName];
    [extra_info appendString:@"--"];
    [extra_info appendString:@"appVersion:"];//app应用版本
    [extra_info appendString:infoDictionary[@"CFBundleShortVersionString"]];
    [extra_info appendString:@"--"];
    [extra_info appendString:@"manufacture:"];//手机厂商
    [extra_info appendString:[[UIDevice currentDevice] model]];
    [extra_info appendString:@"--"];
    [extra_info appendString:@"phoneModel:"];//手机型号
    [extra_info appendString:[self deviceModelName]];
    [extra_info appendString:@"--"];
    [extra_info appendString:@"systemVersion:"];//系统版本
    [extra_info appendString:[[UIDevice currentDevice] systemVersion]];
    [request setValue:extra_info forHTTPHeaderField:@"extra_info"];
    
    
    self.socket = [[SRWebSocket alloc] initWithURLRequest:request protocols:nil allowsUntrustedSSLCertificates:false];
    self.socket.delegate = self;
    //开始连接
    [self.socket open];
    ////NSLog(@"连接的socket地址：%@",self.socket.url.absoluteString);
}
-(void)SRWebSocketClose{
    //    ////NSLog(@"停止socket连接");
    if (_socket){
        [self.socket closeWithCode:4001 reason:nil];
        _socket = nil;
        //断开连接时销毁心跳
    }
    [self destoryHeartBeat];
}
// 需要#import <sys/utsname.h>
-(NSString*)deviceModelName
{
    struct utsname systemInfo;
    uname(&systemInfo);
    NSString *deviceModel = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
    return deviceModel;
}
#define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self
- (void)sendData:(id)data isHeart:(BOOL)isHeart{
    WeakSelf(ws);
    dispatch_queue_t queue =  dispatch_queue_create("zy", NULL);
    dispatch_async(queue, ^{
        if (weakSelf.socket != nil) {
            // 只有 SR_OPEN 开启状态才能调 send 方法啊，不然要崩
            if (weakSelf.socket.readyState == SR_OPEN) {// 只有 SR_OPEN 开启状态才能调 send 方法啊，不然要崩
                if (isHeart) {
                    [weakSelf.socket sendString:data error:nil];
                }else{
                    [weakSelf.socket sendData:data error:nil];
                }
            } else if (weakSelf.socket.readyState == SR_CONNECTING) {
                ////NSLog(@"正在连接中，重连后其他方法会去自动同步数据");
                // 每隔2秒检测一次 socket.readyState 状态，检测 10 次左右
                // 只要有一次状态是 SR_OPEN 的就调用 [ws.socket send:data] 发送数据
                // 如果 10 次都还是没连上的，那这个发送请求就丢失了，这种情况是服务器的问题了，小概率的
                // 代码有点长，我就写个逻辑在这里好了
                [self reConnect];
                
            } else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {
                // websocket 断开了，调用 reConnect 方法重连
                [self reConnect];
            }
        } else {
            [self reConnect];
            ////NSLog(@"没网络，发送失败，一旦断网 socket 会被我设置 nil 的");
        }
    });
}

#pragma mark - private mothodes
//重连机制
- (void)reConnect
{
    [self SRWebSocketClose];
    //重连时间2的指数级增长
    if (reConnectTime == 0) {
        reConnectTime = 2;
    }else{
        reConnectTime *= 2;
    }
    //一直重连
    if (reConnectTime >= 32) {
        reConnectTime = 32;
    }
    
    ////NSLog(@"--------重连--------,%f",reConnectTime);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self SRWebSocketOpenWithURLString:self.urlString];
    });
    
}

#pragma mark----心跳
//取消心跳
- (void)destoryHeartBeat
{
    dispatch_main_async_safe(^{
        if (self->heartBeat) {
            if ([self->heartBeat respondsToSelector:@selector(isValid)]){
                if ([self->heartBeat isValid]){
                    [self->heartBeat invalidate];
                    self->heartBeat = nil;
                }
            }
        }
    })
}

//初始化心跳
- (void)initHeartBeat
{
    dispatch_main_async_safe(^{
        [self destoryHeartBeat];
        /*编辑发送心跳时间 3秒改为10秒一次*/
        //心跳设置为3分钟，NAT超时一般为5分钟
        self->heartBeat = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
        //和服务端约定好发送什么作为心跳标识，尽可能的减小心跳包大小
        [[NSRunLoop currentRunLoop] addTimer:self->heartBeat forMode:NSRunLoopCommonModes];
    })
}

-(void)sentheart{
    if (self.isConnectOK) {
        //发送心跳 和后台可以约定发送什么内容  一般可以调用ping  我这里根据后台的要求 发送了data给他
        //        ////NSLog(@"--------心跳------");
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"status":@(10000),@"userData":@"heartBeat"} options:0 error:nil];
        NSString *strJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        [self sendData:strJson isHeart:YES];
    }
}
#pragma mark - socket delegate
-(void)snedSureRespond{
    //    /*收到消息发送肯定应答*/
    //    NSData *data= [NSJSONSerialization dataWithJSONObject:@{@"status":@(3)} options:NSJSONWritingPrettyPrinted error:nil];
    //    [self sendData:data];
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    if (webSocket == self.socket) {
        self.isConnectOK = YES;
        ////NSLog(@"**** socket 连接成功******* ");
    }
    //每次正常连接的时候清零重连时间
    reConnectTime = 0;
    //开启心跳
    [self initHeartBeat];
    self.socketStateChange(self.socketReadyState);
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    if (webSocket == self.socket) {
        self.isConnectOK = NO;
        ////NSLog(@"***** socket 连接失败*** %@",error);
        [self reConnect];
    }
    self.socketStateChange(self.socketReadyState);
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    ////NSLog(@"***** socket连接断开code----%ld*** ",code);
    self.socketStateChange(self.socketReadyState);
    if (webSocket == self.socket) {
        self.isConnectOK = NO;
        /*只有在非服务器端进行的断开 才进行重连*/
        if (code == 3001) {
            [self SRWebSocketClose];
            [self performSelector:@selector(reConnect) withObject:nil afterDelay:2];
        }else{
            [self reConnect];
        }
    }
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message  {
    if (webSocket == self.socket) {
        ///将数据发送出去
        NSString *mesStr = [NSString stringWithFormat:@"%@",message];
        NSData *JSONData = [mesStr dataUsingEncoding:NSUTF8StringEncoding];
        // 转换为字典
        NSDictionary *dataDict = [NSJSONSerialization JSONObjectWithData:JSONData options:NSJSONReadingMutableLeaves error:nil];
        NSString *status = [NSString stringWithFormat:@"%@",dataDict[@"status"]];
        if ([status isEqualToString:@"10000"]) {
            //心跳
        }else{
            self.scoketMessageBlock(dataDict);
        }
    }
}
/*该函数是接收服务器发送的pong消息，其中最后一个是接受pong消息的，
 在这里就要提一下心跳包，一般情况下建立长连接都会建立一个心跳包，
 用于每隔一段时间通知一次服务端，客户端还是在线，这个心跳包其实就是一个ping消息，
 我的理解就是建立一个定时器，每隔十秒或者十五秒向服务端发送一个ping消息，这个消息可是是空的
 */
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{
    NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
    ////NSLog(@"reply===%@",reply);
}

#pragma mark - **************** setter getter
- (SRReadyState)socketReadyState{
    return self.socket.readyState;
}
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


@end
